package gameoflive;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;


public class GameOfLive extends AbstractRunnable implements IRenderer
{
    private static GameOfLive instance;
    
    public synchronized static GameOfLive getInstance()
    {
        if (instance == null)
        {
            instance = new GameOfLive();
        }
        return instance;
    }
    
    private Point           maxXY;
    
    private List<Cell>      cells;
    
    private int             generation;
    private int             livingCells          = 0;
    private int             livingCellsIdentical = 0;
    
    private GameOfLiveRules rules                = GameOfLiveRules.DEFAULT;
    
    public void setRule(String rule)
    {
        try
        {
            GameOfLiveRules newRule = new GameOfLiveRules(rule);
            pause();
            rules = newRule;
            resume();
        }
        catch(Exception ex)
        {
            Game.log(ex);
        }
    }
    
    // Lebende Zellen generieren
    private static Random RANDOM = new Random(System.currentTimeMillis());

    private Cell getCell(Point position)
    {
        return getCell((int) position.getWidth(), (int) position.getHeight());
    }
    
    public Cell getCell(int x, int y)
    {
        if (x < 0 || x > maxXY.getWidth())
        {
            throw new IndexOutOfBoundsException(x + " not in 0-" + maxXY.getWidth());
        }
        if (y < 0 || y > maxXY.getHeight())
        {
            throw new IndexOutOfBoundsException(y + " not in 0-" + maxXY.getHeight());
        }
        int index = calculateIndex(x, y);
        return cells.get(index);
    }
    
    private int calculateIndex(int x, int y)
    {
        int index = (maxXY.getWidth() * y) + x;
        return index;
    }
    
    private Point calculatePosition(int index)
    {
        int x = 0;
        int y = 0;
        // find y
        y = index / maxXY.getX();
        x = index - (y * maxXY.getX());
        return new Point(x, y);
    }
    
    private void prepareNextGeneration()
    {
        generation++;
        int alive = 0;
        for (int i = 0; i < cells.size(); i++)
        {
            Cell cell = cells.get(i);
            int livingNeighbors = cell.countLivingNeighbors();
            cell.setSurviving(rules.isSurviving(cell.isAlive(), livingNeighbors));
            if (cell.isAlive())
            {
                alive++;
            }
        }
        if (alive == livingCells)
        {
            livingCellsIdentical++;
        }
        else
        {
            livingCellsIdentical = 0;
        }
        livingCells = alive;
        setStatus("Generation " + generation + "; " + livingCells + " Living Cells");
        if (livingCells <= 0)
        {
            Game.log("No more living cells");
            requestRestart();
        }
        else if (livingCellsIdentical > 50)
        {
            requestRestart();
        }
    }
    
    private int prozentLivingCells = 15;
    
    public void setProzentLivingCells(int prozentLivingCells)
    {
        if (prozentLivingCells > 100)
        {
            prozentLivingCells = 100;
        }
        this.prozentLivingCells = prozentLivingCells;
    }
    
    private void addLivingCells()
    {
        int count = (cells.size() / 100) * prozentLivingCells;
        long startTime = System.nanoTime();
        if (isRunning() && isPaused() == false)
        {
            pause();
        }
        List<Point> livingCells = new ArrayList<Point>();
        do
        {
            Point position = calculatePosition(RANDOM.nextInt(cells.size()));
            if (position.getX() > 0 && position.getY() > 0 && livingCells.contains(position) == false)
            {
                livingCells.add(position);
            }
        }
        while (livingCells.size() < count);
        // reset count
        count = 0;
        for (Point position : livingCells)
        {
            Cell cell = getCell(position);
            cell.setAlive(true);
            cell.setSurviving(true);
            count++;
        }
        Game.log(count + " living cells added (" + Util.nanoToSec(System.nanoTime() - startTime) + " sec)");
        if (isRunning() && isPaused())
        {
            resume();
        }
    }
    
    private boolean requestRestart = false;
    private boolean requestStop    = false;
    private Point   newSize        = null;
    
    public void requestRestart(Point newSize)
    {
        requestRestart = true;
        this.newSize = newSize;
        Game.log("REQUEST RESTART size: " + newSize);
    }

    public void requestRestart()
    {
        requestRestart(null);
    }
    
    public void requestStop()
    {
        requestStop = true;
        Game.log("REQUEST STOP");
    }
    
    private void resetGeneration()
    {
        pause();
        long startTime = System.nanoTime();
        for (Iterator<Cell> i = cells.iterator(); i.hasNext();)
        {
            Cell cell = i.next();
            cell.setAlive(false);
            cell.setSurviving(false);
        }
        generation = 0;
        Game.log("resetGeneration() (" +  + Util.nanoToSec(System.nanoTime() - startTime)  + " sec)");
    }
    
    private void restart()
    {
        resetGeneration();
        addLivingCells();
        start();
        requestRestart = false;
    }
    
    @Override
    public void stopped()
    {
        super.stopped();
        requestStop = false;
    }
    
    private void initGeneration(Point xy)
    {
        // Anzahl Zellen
        long startTime = System.nanoTime();
        maxXY = new Point(xy.getX(), xy.getY());
        int count = maxXY.getX() * maxXY.getY();
        // Liste generieren
        if (cells != null && cells.isEmpty() == false)
        {
            while (cells.size() > count)
            {
                cells.remove(cells.size() - 1);
            }
        }
        else
        {
            cells = new ArrayList<Cell>();
        }
        while (cells.size() < count)
        {
            cells.add(new Cell(false, false));
        }
        for (int i = 0; i < cells.size(); i++)
        {
            Cell cell = cells.get(i);
            Point position = calculatePosition(i);
            // RIGHT:
            Point RIGHT = new Point(position.getX(), position.getY());
            RIGHT.setX(RIGHT.getX() + 1);
            if (RIGHT.getX() >= maxXY.getX())
            {
                RIGHT.setX(0);
            }
            cell.setRight(getCell(RIGHT));
            // BOTTOM:
            Point BOTTOM = new Point(position.getX(), position.getY());
            BOTTOM.setY(BOTTOM.getY() + 1);
            if (BOTTOM.getY() >= maxXY.getY())
            {
                BOTTOM.setY(0);
            }
            cell.setBottom(getCell(BOTTOM));
            // LEFT:
            Point LEFT = new Point(position.getX(), position.getY());
            LEFT.setX(LEFT.getX() - 1);
            if (LEFT.getX() < 0)
            {
                LEFT.setX(maxXY.getX() - 1);
            }
            cell.setLeft(getCell(LEFT));
            // TOP:
            Point TOP = new Point(position.getX(), position.getY());
            TOP.setY(TOP.getY() - 1);
            if (TOP.getY() < 0)
            {
                TOP.setY(maxXY.getY() - 1);
            }
            cell.setTop(getCell(TOP));
            // BOTTOMLEFT:
            Point BOTTOMLEFT = new Point(position.getX(), position.getY());
            BOTTOMLEFT.setX(LEFT.getX());
            BOTTOMLEFT.setY(BOTTOM.getY());
            cell.setBottomLeft(getCell(BOTTOMLEFT));
            // BOTTOMRIGHT:
            Point BOTTOMRIGHT = new Point(position.getX(), position.getY());
            BOTTOMRIGHT.setX(RIGHT.getX());
            BOTTOMRIGHT.setY(BOTTOM.getY());
            cell.setBottomRight(getCell(BOTTOMRIGHT));
            // TOPLEFT:
            Point TOPLEFT = new Point(position.getX(), position.getY());
            TOPLEFT.setX(LEFT.getX());
            TOPLEFT.setY(TOP.getY());
            cell.setTopLeft(getCell(TOPLEFT));
            // TOPRIGHT:
            Point TOPRIGHT = new Point(position.getX(), position.getY());
            TOPRIGHT.setX(RIGHT.getX());
            TOPRIGHT.setY(TOP.getY());
            cell.setTopRight(getCell(TOPRIGHT));
        }
        Game.log("initGeneration(" + xy.getX() + "/" + xy.getY() + ") complete, " + cells.size() + " cells created (" +  + Util.nanoToSec(System.nanoTime() - startTime)  + " sec)");
        addLivingCells();
        generation = 0;
        start();
        requestRestart = false;
        newSize = null;
    }
    
    private ColorSet colorSet = ColorSet.BLACK_WHITE;
    
    public void setColorSet(ColorSet colorSet)
    {
        this.colorSet = colorSet;
    }
    
    public ColorSet getColorSet()
    {
        return colorSet;
    }
    
    @Override
    public void render(Graphics g, int cellSize)
    {
        if (isRunning())
        {
            for (int i = 0; i < cells.size(); i++)
            {
                Cell cell = cells.get(i);
                g.setColor(colorSet.getColor(cell.isAlive(), cell.isSurviving()));
                Point position = calculatePosition(i);
                g.fillRect((int) position.getWidth() * cellSize, (int) position.getHeight() * cellSize, cellSize, cellSize);
                if (isRunning() && isPaused() == false)
                {
                    // next round
                    cell.setAlive(cell.isSurviving());
                }
            }
        }
    }
    
    public void update()
    {
        if (requestStop)
        {
            stopped();
        }
        else if (requestRestart)
        {
            if (newSize == null)
            {
                restart();
            }
            else
            {
                initGeneration(newSize);
            }
        }
        else
        {
            if (isRunning() && isPaused() == false)
            {
                prepareNextGeneration();
            }
        }
        
    }
}
